package database

import (
	dict2 "abdis/datastruct/dict"
	"abdis/interface/database"
	"abdis/interface/datastruct"
	"abdis/interface/redis"
	"abdis/lib/utils"
	"abdis/redis/protocol"
	"strconv"
	"strings"
)

func (db *DB) getAsDict(key string) (datastruct.Dict, protocol.ErrorReply) {
	entity, exists := db.GetEntity(key)
	if !exists {
		return nil, nil
	}
	dict, ok := entity.Data.(datastruct.Dict)
	if !ok {
		return nil, protocol.MakeWrongTypeErrReply()
	}
	return dict, nil
}

func (db *DB) getOrInitDict(key string) (dict datastruct.Dict, isNew bool, errReply protocol.ErrorReply) {
	dict, errReply = db.getAsDict(key)
	if errReply != nil {
		return nil, false, errReply
	}
	if dict == nil {
		dict = dict2.MakeSimple()
		db.PutEntity(key, &database.DataEntity{Data: dict})
		isNew = true
	}
	return
}

func checkDictAndReplyValid(dict datastruct.Dict, reply protocol.ErrorReply) redis.Reply {
	if reply != nil {
		return reply
	}
	if dict == nil {
		return protocol.MakeNullBulkReply()
	}
	return nil
}

func undoHSet(db *DB, args [][]byte) []database.CmdLine {
	return rollbackHashFields(db, string(args[0]), string(args[1]))
}

func undoHMSet(db *DB, args [][]byte) []database.CmdLine {
	key := string(args[0])
	args = args[1:]
	size := len(args) / 2
	fields := make([]string, size)
	for i := 0; i < size; i++ {
		fields[i] = string(args[2*i])
	}
	return rollbackHashFields(db, key, fields...)
}

func undoHDel(db *DB, args [][]byte) []database.CmdLine {
	key := string(args[0])
	fields := utils.BytesToStrings(args[1:])
	return rollbackHashFields(db, key, fields...)
}

// HSET key field value
func execHSet(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	field := string(args[1])
	val := args[2]

	dict, _, errReply := db.getOrInitDict(key)
	if errReply != nil {
		return errReply, false
	}

	ret := dict.Put(field, val)
	return protocol.MakeIntReply(int64(ret)), true
}

func execHLen(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])

	dict, errReply := db.getAsDict(key)
	if reply := checkDictAndReplyValid(dict, errReply); reply != nil {
		return reply, false
	}

	return protocol.MakeIntReply(int64(dict.Len())), true
}

func execHDel(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	fields := utils.BytesToStrings(args[1:])

	dict, errReply := db.getAsDict(key)
	if reply := checkDictAndReplyValid(dict, errReply); reply != nil {
		return reply, false
	}

	deleted := 0
	for _, field := range fields {
		_, result := dict.Remove(field)
		deleted += result
	}

	if dict.Len() == 0 {
		db.Remove(key)
	}

	return protocol.MakeIntReply(int64(deleted)), true
}

func execHMSet(db *DB, args [][]byte) (redis.Reply, bool) {
	if len(args)%2 != 1 {
		return protocol.MakeSyntaxErrReply(), false
	}
	key := string(args[0])
	size := (len(args) - 1) / 2

	dict, _, errReply := db.getOrInitDict(key)
	if reply := checkDictAndReplyValid(dict, errReply); reply != nil {
		return reply, false
	}

	args = args[1:]
	for i := 0; i < size; i++ {
		dict.Put(string(args[2*i]), args[2*i+1])
	}
	return protocol.MakeOkReply(), true
}

func execHScan(db *DB, args [][]byte) (redis.Reply, bool) {
	count := 10
	pattern := "*"
	if len(args) > 2 {
		for i := 2; i < len(args); i++ {
			arg := strings.ToLower(string(args[i]))
			if arg == "count" {
				num, err := strconv.Atoi(string(args[i+1]))
				if err != nil {
					return protocol.MakeSyntaxErrReply(), false
				}
				count = num
				i++
			} else if arg == "match" {
				pattern = string(args[i+1])
				i++
			} else {
				return protocol.MakeSyntaxErrReply(), false
			}
		}
	} else if len(args) < 2 {
		return protocol.MakeSyntaxErrReply(), false
	}

	// 处理完前置准备工作
	key := string(args[0])
	dict, errReply := db.getAsDict(key)
	if reply := checkDictAndReplyValid(dict, errReply); reply != nil {
		return reply, false
	}
	cursor, err := strconv.Atoi(string(args[1]))
	if err != nil {
		return protocol.MakeErrReply("ERR invalid cursor"), false
	}

	keys, next := dict.DictScan(cursor, count, pattern)
	if next < 0 {
		return protocol.MakeErrReply("ERR invalid argument"), false
	}

	ret := []redis.Reply{
		protocol.MakeBulkReply([]byte(strconv.FormatInt(int64(next), 10))),
		protocol.MakeMultiBulkReply(keys),
	}
	return protocol.MakeMultiRawReply(ret), true
}

func init() {
	registerCommand("HSet", execHSet, writeFirstKey, undoHSet, 4, flagWrite)
	registerCommand("HMSet", execHMSet, writeFirstKey, undoHMSet, -4, flagWrite)

	registerCommand("HDel", execHDel, writeFirstKey, undoHDel, -3, flagWrite)

	registerCommand("HLen", execHLen, readFirstKey, nil, 2, flagReadOnly)

	registerCommand("HScan", execHScan, readFirstKey, nil, -2, flagReadOnly)
}
